import sys
sys.path.append('../')
from madina.zonal.zonal import Zonal
import madina.una.tools as una
cambridge = Zonal()
Since there is no GUI ()graphical user interface), whenever you need to know details about your Zonal object, call the describe() function
cambridge.describe()
No zonal_layers yet, load a layer using 'load_layer(layer_name, file_path)' Geographic center: (None, None) No network graph yet. First, insert a layer that contains network segments (streets, sidewalks, ..) and call create_street_network(layer_name, weight_attribute=None) Then, insert origins and destinations using 'insert_nodes(label, layer_name, weight_attribute)' Finally, when done, create a network by calling 'create_street_network()'
.geoJSON and .shp. To load a layer, call the function load_layer() giving it a layer name for your new layer, and a path to where the data is stored.
cambridge.load_layer(
layer_name="building_entrances",
file_path="./Data/building_entrances.geojson"
)
cambridge.load_layer(
layer_name="subway",
file_path="./Data/subway.geojson"
)
cambridge.load_layer(
layer_name="sidewalks",
file_path="./Data/sidewalks.geojson"
)
.create_deck_map()function. to save the map as a file, call the same function, with a save_as=cambridge_map.html". Colors are set randomly, but we'll go over map styling in a later lab. The current map visualizations utilize a library called PyDeck, a python wrapper for the javascript library Deck.GL open-sourced by a major ride-hailing provider that's based on the WebGL powertful 3D visualization framework made by a non-profit organization. Take this moment to appreciate the value of open source work here that made this possible (and free) and consider pushing for more open sourced projects in your future life. cambridge.describe() to get a summary of your layers, their current settings and what columns are in each layer. This would be very handy when you're trying to debug for errors, especially when you start using data from external sources that might have different naming conventions, or when you're trying to deal with coordinate reference systems or projection systems.cambridge.create_map()
You can easily specify a color for each layer:
cambridge.create_map(
[
{'layer': 'sidewalks', 'color': [125, 125, 125]},
{'layer': 'subway', 'color': [255, 0, 0]},
{'layer': 'building_entrances', 'color': [0, 0, 255]}
]
)
cambridge.describe()
Layer name | Visible | projection | rows | File path building_entrances | 1 | EPSG:3857 | 118 | ./Data/building_entrances.geojson subway | 1 | EPSG:3857 | 2 | ./Data/subway.geojson sidewalks | 1 | EPSG:3857 | 170 | ./Data/sidewalks.geojson Geographic center: (-0.014369793082880312, 0.0016430747641603674) No network graph yet. First, insert a layer that contains network segments (streets, sidewalks, ..) and call create_street_network(layer_name, weight_attribute=None) Then, insert origins and destinations using 'insert_nodes(label, layer_name, weight_attribute)' Finally, when done, create a network by calling 'create_street_network()'
create_network_nodes_edges() function does exactly that, we just need to give it a parameter source_layer="sidewalks" to let it know we're constructing a network from the sidewalk layer. When called, this function create two layers internally: network_nodes and network_edges. the network_edges table is equivelant to what's known as an edge list in graph theory.
cambridge.create_street_network(source_layer="sidewalks")
to see what the network looks like, we can call the cambridge.create_map() giving it a list of the layers we want to see.
cambridge.create_map(
layer_list=[
{'gdf': cambridge.network.nodes, 'color': [255, 0, 255]},
{'gdf': cambridge.network.edges, 'color': [125, 125, 125]}
]
)
# inserting origins:
cambridge.insert_node(
label='origin',
layer_name="subway",
)
# inserting destinations
cambridge.insert_node(
label='destination',
layer_name="building_entrances",
)
As a convention in this library, there are a few default colors when visualizing the network_nodes and network_edges layers:
create_network_nodes_edges() is a clean geometry where the end of a line exactly touches the begenning of another one to establish a connection. cambridge.create_map(
layer_list=[
{'gdf': cambridge.network.nodes, 'color_by_attribute': 'type', 'color_method': 'categorical'},
{'gdf': cambridge.network.edges, 'color': [125, 125, 125]}
]
)
create_graph() function that creates a "graph" object for your analysis.
cambridge.create_graph()
una_accessibility and giving it parameters reach=True, search_radius=300 will measure how many how many destinations (building entrances) are reachable from origins (subway).
We see that the northen station could be reached by 106 building entrance, while the southern station could be reached by 112 building entrance.
The una_accessibility creates a column called una_reach when the parameter reach=True. We could use that to explore more visualization functionalities:
una.accessibility(
cambridge,
reach=True,
search_radius=300
)
cambridge.create_map(
layer_list=[
{"gdf": cambridge.network.edges, 'color': [125, 125, 125]},
{"gdf": cambridge.network.nodes, "radius": "una_reach", 'text':'una_reach', 'color': [255, 0, 0]},
]
)
Setting the parameter weight='people' enables us to weight the destinations (building entrances) by how many people actually live in these building. We could then see that 2,789 people could reach the northen station by walking a maximum of 300 meters. The southern station could be reached by 3,017 people.
una.accessibility(
cambridge,
reach=True,
search_radius=300,
weight='people'
)
cambridge.create_map(
layer_list=[
{"gdf": cambridge.network.edges, 'color': [125, 125, 125]},
{"gdf": cambridge.network.nodes, "radius": "una_reach", 'text':'una_reach', 'color': [255, 0, 0]},
]
)
service_area() function is a great introduction into that. The library internally uses a library called GeoPandas, where a layer is usually stored as a GeoDataFrame. GeoPandas are an extenstion to the well known Pandas library. If you're familiar with the Pandas Dataframe, you'll be able to use all what you know about a Dataframe. The only difference is that a GeoDataFrame always has a geometry columns, and supports a wide array of spatial operations.
The service_area() function returns a GeoDataFrame destinations containing all the destinations covered by an origin's service area, and a GeoDataframe network_edges containing all network segments inside the origin's service area. A Third Dataframe scope_gdf contains the boundaries of the service area. Notice how the function create_deck_map() could take either a layer from the layers we loaded, or a gdf (i.e. GeoDataFrame) resulting from a process.
destinations, network_edges, scope_gdf = una.service_area(
cambridge,
#[120],
search_radius=100,
)
cambridge.create_map(
layer_list=[
{"layer": 'sidewalks'},
{"layer": 'building_entrances'},
{"gdf": network_edges, "color": [0, 255, 0]},
{"gdf": destinations, "color": [255, 0, 0]},
{"gdf": scope_gdf[scope_gdf['name'] == 'service area border'], "color": [0, 0, 255], 'opacity': 0.10},
]
)
set_attribute() function allows for that.
While in a visual interface, it is possible to select the elements you want with a mouse. In a coding environment, every object have an identifier, and each layer once loaded is assigned an id attribute. Hover over tha previous map to find the building entrances with ids 2 and 115, They are the same one referref to in the Rhino bullet point 19. They are both in layer="building_entrances and we want to set a attribute='student' to values 10 and 10, for ids 2 and 115 respectively.
From the map below, we now notice that 20 students can reach the northen station, and 10 students could reach he southern station on a 300 meter walk.
cambridge.create_map(
layer_list=[
{"gdf": cambridge.network.edges, 'color': [125, 125, 125]},
{"gdf": cambridge.network.nodes.reset_index(), 'text':'id', 'color': [255, 0, 0]},
]
)
cambridge.create_map(
layer_list=[
{"gdf": cambridge.network.edges, 'color': [125, 125, 125]},
{"gdf": cambridge.network.nodes.loc[[2, 115]].reset_index(), 'text':'id', 'color': [255, 0, 0]},
]
)
cambridge.layers['building_entrances'].gdf.at[2, 'students'] = 10
cambridge.layers['building_entrances'].gdf.at[115, 'students'] = 10
una.accessibility(
cambridge,
reach=True,
search_radius=300,
weight='students'
)
cambridge.create_map(
layer_list=[
{"gdf": cambridge.network.edges, 'color': [125, 125, 125]},
{"gdf": cambridge.network.nodes, "radius": "una_reach", 'text':'una_reach', 'color': [255, 0, 0]},
]
)
When factoring in a gravity decay, we notice that the northen station only attracts 16.88 students (As opposed to 20) and the southern station now attracts only 8.67 students (as opposed to 10).
una.accessibility(
cambridge,
gravity=True,
search_radius=300,
beta=0.001,
alpha=1,
weight='students',
)
cambridge.create_map(
layer_list=[
{"gdf": cambridge.network.edges, 'color': [125, 125, 125]},
{"gdf": cambridge.network.nodes, "radius": "una_gravity", 'text':'una_gravity', 'color': [255, 0, 0]},
]
)
people as weight, and we again call the function una_accessibility(), this time, setting both reach and gravity to True
Hovering over the map below to where the stations are, we could see that the northern station has a reach index of 2,789 bur a gravity index of only 2,361.54, The southern station has a reach index of 3,017 and a gravity_index of only 2,606.81.
una.accessibility(
cambridge,
reach=True,
gravity=True,
search_radius=300,
beta=0.001,
alpha=1,
weight='people',
)
cambridge.create_map(
layer_list=[
{"gdf": cambridge.network.edges, 'color': [125, 125, 125]},
{"gdf": cambridge.network.nodes, "radius": "una_gravity", 'text':'una_gravity', 'color': [255, 0, 0]},
]
)
One note about the una_accessibility(), there will be overlap between destinations assigned to each origin, so a destination would be double counted in an origin's accissability indices. The closest_facility() assigns each destination (in this case, building entrance) to one single origin (a subway station in this case)
| Function | una.accessibility() |
una.closest_facility() |
||
| Station | Reach | Graviy | Reach | Gravity |
| Northren | 2,789.00 | 2.361.54 | 1,265.00 | 1,120.92 |
| Southren | 3,017.00 | 2,606.81 | 1,914.00 | 1,716.57 |
This variation of values (across the same station), poses the question of What value is closest to reality? This is an ongoing research topic, and urban scientists are able to choose better models the more we learn about pedestrian activity through data,
una.closest_facility(
cambridge,
weight='people',
gravity=True,
reach=True,
search_radius=300,
beta=0.001
)
cambridge.create_map(
layer_list=[
{"gdf": cambridge.network.edges, 'color': [125, 125, 125]},
{"gdf": cambridge.network.nodes,"text": "una_closest_destination_distance", "color_by_attribute": "una_closest_destination", "color_method": "categorical"},#, 'radius': "una_reach"},
]
)
subway to building_entrances and destinations from building_entrances to subway.
In order to do this, we need to first clear up the origins and destination nodes we created earlier, bur we could still use the same neteowk edges (sidewalks) as before, since changing the origins and destinations will not impact the underlying network segments and intersections. Calling the function clear_nodes() empties out our existing oeigins and destinations. We then need to insert origins and destinations, then create a graph object, just like before.
cambridge.clear_nodes()
cambridge.insert_node(
label='origin',
layer_name="building_entrances",
)
cambridge.insert_node(
label='destination',
layer_name="subway",
)
cambridge.create_graph()
The reach index shown below, shows how many subway station could be reached from each building enterance, by walking 300 meters. Most building entrances can access two stations, but buildings on the phrepherey, could only access one of the two stations within a 200 meter walk,
una.accessibility(
cambridge,
reach=True,
search_radius=300,
)
cambridge.create_map(
layer_list=[
{"gdf": cambridge.network.nodes, "color_by_attribute": "una_reach", 'color_method': 'gradient', 'text':'una_reach'},
{"gdf": cambridge.network.edges, 'color': [125, 125, 125]}
]
)
As for the gravity index, the interpretation of what these values mean is sensitive to the bets parameter which could translate to the "area's willingness to walk". living in a building with a high gravity index, means that you have more subway options that are close and could easily be reached on foot. A low gravity index, means that you live in a building with fewer subway options that are further out.
una.accessibility(
cambridge,
reach=True,
gravity=True,
search_radius=300,
beta=0.004,
alpha=1,
)
cambridge.create_map(
layer_list=[
{"gdf": cambridge.network.nodes, "color_by_attribute": "una_gravity", 'color_method':'gradient', 'text': 'una_gravity'},
{"gdf": cambridge.network.edges, 'color': [135, 125, 125]}
]
)